package solver

import (
	"log"
	"slices"
)

type Discard struct{}

func (Discard) Write(p []byte) (n int, err error) {
	return
}

func init() {
	log.SetFlags(log.Ltime | log.Lmicroseconds)
	log.SetPrefix("Sudoku: ")
	//log.SetOutput(Discard{})
}

func merge(r [][]int, x int) [][]int {
	if len(r) == 0 {
		return [][]int{{x}}
	}
	for i, xs := range r {
		r[i] = append([]int{x}, xs...)
	}
	return r
}

func combine(xs []int, n int) [][]int {
	if n < 1 {
		return [][]int{}
	}
	if n == 1 {
		r := [][]int{}
		for _, x := range xs {
			r = append(r, []int{x})
		}
		return r
	}
	length := len(xs)
	if length < n {
		return [][]int{}
	}
	if length == n {
		return [][]int{xs}
	}
	return append(merge(combine(xs[1:], n-1), xs[0]), combine(xs[1:], n)...)
}

func calcBoxAndCellNum(rowNum int, colNum int) (boxNum int, cellNum int) {
	boxNum = rowNum/3*3 + colNum/3
	cellNum = rowNum%3*3 + colNum%3
	return
}

func calcRowAndColNum(boxNum int, cellNum int) (rowNum int, colNum int) {
	rowNum = boxNum/3*3 + cellNum/3
	colNum = boxNum%3*3 + cellNum%3
	return
}

const (
	RowHouse int = iota
	ColHouse
	BoxHouse
)

func HouseType(x, y *Cell) int {
	if x.rowNum == y.rowNum {
		return RowHouse
	} else if x.colNum == y.colNum {
		return ColHouse
	} else if x.boxNum == y.boxNum {
		return BoxHouse
	} else {
		return 0
	}
}

func (h *House) HouseType() int {
	return HouseType(h.cells[0], h.cells[4])
}

func (h *House) HouseTypeName() string {
	names := [3]string{"Row", "Col", "Box"}
	return names[HouseType(h.cells[0], h.cells[4])]
}

func sumupCandCells(house House) (candCells [9][9]bool, candCounts [9]int) {
	for cellNum, cell := range house.cells {
		if cell.solved() {
			continue
		}
		for cand, candidate := range cell.candidates {
			if candidate {
				candCells[cand][cellNum] = true
				candCounts[cand]++
			}
		}
	}
	return
}

func (g *Grid) doStrategy(doStrategyHouse func(House) bool) (used bool) {
	for _, box := range g.boxes {
		if doStrategyHouse(box) {
			used = true
		}
	}

	for _, row := range g.rows {
		if doStrategyHouse(row) {
			used = true
		}
	}

	for _, col := range g.cols {
		if doStrategyHouse(col) {
			used = true
		}
	}
	return
}

func intersect(as []int, bs []int) []int {
	cs := []int{}
	for _, a := range as {
		if slices.Contains(bs, a) {
			cs = append(cs, a)
		}
	}
	return cs
}

func union(as []int, bs []int) []int {
	cs := []int{}
	cs = append(cs, as...)
	cs = append(cs, bs...)
	slices.Sort(cs)
	cs = slices.Compact(cs)
	return cs
}

func difference(as []int, bs []int) []int {
	cs := []int{}
	for _, a := range as {
		if !slices.Contains(bs, a) {
			cs = append(cs, a)
		}
	}
	return cs
}

func symDifference(as []int, bs []int) []int {
	return difference(union(as, bs), intersect(as, bs))
}

func (g *Grid) validateGivens() bool {
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if g.givens[i][j] != 0 && g.givens[i][j] != g.cells[i][j].value {
				log.Printf("r%vc%v givens=%v solved=%v\n", i, j, g.givens[i][j], g.cells[i][j].value)
				return false
			}
		}
	}
	return true
}

func (g *Grid) Validate() bool {
	if g.solvedCount != 81 {
		log.Println("solvedCount:", g.solvedCount)
		return false
	}

	if !g.validateGivens() {
		log.Println("the givens are modified")
		return false
	}

	for _, row := range g.rows {
		if !validateHouse(row) {
			log.Printf("row %v error\n", row.cells[0].rowNum)
			return false
		}
	}

	for _, col := range g.cols {
		if !validateHouse(col) {
			log.Printf("col %v error\n", col.cells[0].colNum)
			return false
		}
	}

	for _, box := range g.boxes {
		if !validateHouse(box) {
			log.Printf("box %v error\n", box.cells[0].boxNum)
			return false
		}
	}
	return true
}

func validateHouse(house House) (ok bool) {
	ok = true

	if house.solvedCount != 9 {
		ok = false
		log.Println("house.solvedCount:", house.solvedCount)
	}

	values := [9]bool{}
	for _, cell := range house.cells {
		if 1 <= cell.value && cell.value <= 9 {
			values[cell.value-1] = true
		}
	}

	if values != [9]bool{true, true, true, true, true, true, true, true, true} {
		ok = false
		log.Println(values)
	}

	return
}
